package com.github.bryx.workflow.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.github.bryx.workflow.domain.*;
import com.github.bryx.workflow.domain.process.buildtime.TaskTimer;
import com.github.bryx.workflow.dto.runtime.*;
import com.github.bryx.workflow.exception.WorkflowRuntimeException;
import com.github.bryx.workflow.service.WorkflowRuntimeManager;
import com.github.bryx.workflow.service.WorkflowRuntimeQuery;
import com.github.bryx.workflow.service.WorkflowRuntimeService;
import com.github.bryx.workflow.service.dao.*;
import com.github.bryx.workflow.service.process.ProcessService;
import com.github.bryx.workflow.util.CollectionsUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
 * @Author jameswu
 * @Date 2021/6/10
 **/
@Service
@Slf4j
public class WorkflowRuntimeManagerImpl implements WorkflowRuntimeManager, InitializingBean {

    @Autowired
    WorkflowInstanceDao workflowInstanceDao;

    @Autowired
    WorkflowTaskInstanceDao workflowTaskInstanceDao;

    @Autowired
    WorkflowTimerJobDao workflowTimerJobDao;

    @Autowired
    WorkflowTimerInstanceDao workflowTimerInstanceDao;

    @Autowired
    ProcessService processService;

    @Autowired
    WorkflowRuntimeQuery workflowRuntimeQuery;

    @Autowired
    WorkflowInstanceRelationDao workflowInstanceRelationDao;

    @Autowired
    WorkflowRuntimeService workflowRuntimeService;

    @Autowired
    WorkflowInstanceActivityDao workflowInstanceActivityDao;

    @Override
    public String createWorkflowInstance(CreateWorkflowInstanceDto dto) {
        WorkflowInstance workflowInstance = new WorkflowInstance();
        workflowInstance.setCreateTime(new Date());
        workflowInstance.setProcessId(dto.getProcessId());
        workflowInstance.setCreatorId(dto.getCreatorId());
        workflowInstance.setDefId(dto.getDefId());
        workflowInstance.setFormData(dto.getFormData());
        workflowInstance.setDefRevId(dto.getDefRevId());
        workflowInstance.setDeleted(false);
        workflowInstance.setStatus(WorkflowInstance.WorkflowInstanceStatus.ONGOING);
        // TODO set seq
        workflowInstanceDao.save(workflowInstance);
        // 是子流程，需要保存父流程关系
        if (dto.getParentWorkflowTaskInstanceId() != null && dto.getWorkflowInstanceRelationType() != null){
            WorkflowTaskInstance parentWorkflowTaskInstance = workflowTaskInstanceDao.getById(dto.getParentWorkflowTaskInstanceId());
            Validate.notNull(parentWorkflowTaskInstance,"父流程任务节点不存在 {}", dto.getParentWorkflowTaskInstanceId());
            WorkflowInstanceRelation workflowInstanceRelation = WorkflowInstanceRelation.builder()
                    .workflowInstanceId(parentWorkflowTaskInstance.getWorkflowInstanceId())
                    .workflowTaskInstanceId(parentWorkflowTaskInstance.getId())
                    .processTaskDefId(parentWorkflowTaskInstance.getProcessTaskDefId())
                    .subWorkflowInstanceId(workflowInstance.getId())
                    .type(dto.getWorkflowInstanceRelationType())
                    .build();
            workflowInstanceRelationDao.save(workflowInstanceRelation);
        }
        return workflowInstance.getId();
    }

    @Override
    public WorkflowTaskInstance createWorkflowTaskInstance(CreateWorkflowTaskInstanceDto dto) {
        Validate.notNull(dto.getName(), "must provide task name");
        Validate.notNull(dto.getProcessTaskId(), "must provide actitivi process task id");
        Validate.notNull(dto.getWorkflowInstanceId(), "must provide workflow instance id");
        WorkflowTaskInstance taskInstance = new WorkflowTaskInstance();
        BeanUtils.copyProperties(dto, taskInstance);
        taskInstance.setStartTime(new Date());
        taskInstance.setStatus(WorkflowTaskInstance.WorkflowTaskInstanceStatus.RUNNING);
        workflowTaskInstanceDao.save(taskInstance);
        return taskInstance;
    }

    @Override
    public String createWorkflowTimerJob(CreateWorkflowTimerJobDto createWorkflowTimerJobDto){
        Validate.notNull(createWorkflowTimerJobDto.getWorkflowTaskInstanceId(), "please specify valid processTaskId");
        WorkflowTaskInstance workflowTaskInstance = workflowTaskInstanceDao.getById(createWorkflowTimerJobDto.getWorkflowTaskInstanceId());
        Validate.notNull(workflowTaskInstance, "Workflow task instance does not exist");
        Validate.notNull(createWorkflowTimerJobDto.getTimerDefinitionId(), "please specify timeDefinitionId");
        TaskTimer timer = null;
        switch (createWorkflowTimerJobDto.getType()){
            case CRON:
                timer = processService.addTimerOnTask(workflowTaskInstance.getProcessTaskId(), createWorkflowTimerJobDto.getTimerDefinitionId() , createWorkflowTimerJobDto.getCron(), createWorkflowTimerJobDto.getEndDate());
                break;
            case CYCLE:
                timer = processService.addTimerOnTask(workflowTaskInstance.getProcessTaskId(), createWorkflowTimerJobDto.getTimerDefinitionId(), createWorkflowTimerJobDto.getRepeat(), createWorkflowTimerJobDto.getDuration(), createWorkflowTimerJobDto.getTimeUnit(), createWorkflowTimerJobDto.getEndDate());
                break;
            case DURATION:
                timer = processService.addTimerOnTask(workflowTaskInstance.getProcessTaskId(), createWorkflowTimerJobDto.getTimerDefinitionId(), createWorkflowTimerJobDto.getDuration(), createWorkflowTimerJobDto.getTimeUnit());
                break;
            case FIXED_DATE:
                timer = processService.addTimerOnTask(workflowTaskInstance.getProcessTaskId(), createWorkflowTimerJobDto.getTimerDefinitionId(), createWorkflowTimerJobDto.getFixedDate());
                break;
        }
        Validate.notNull(timer, "fail to create timer");
        WorkflowTimerJob workflowTimerJob = WorkflowTimerJob.builder()
                .addon(createWorkflowTimerJobDto.getAddon())
                .workflowInstanceId(workflowTaskInstance.getWorkflowInstanceId())
                .workflowTaskInstanceId(workflowTaskInstance.getId())
                .endDate(createWorkflowTimerJobDto.getEndDate())
                .status(WorkflowTimerJob.Status.CREATED)
                .processTimerDefId(createWorkflowTimerJobDto.getTimerDefinitionId())
                .processTimerJobId(timer.getJobId())
                .nextTriggerTime(timer.getTriggerTime())
                .createTime(new Date())
                .build();
        createWorkflowTimerJobDto.setAddon(null);
        createWorkflowTimerJobDto.setTimerDefinitionId(null);
        createWorkflowTimerJobDto.setWorkflowTaskInstanceId(null);
        workflowTimerJob.setJobConfig(JSON.toJSON(createWorkflowTimerJobDto));
        workflowTimerJobDao.save(workflowTimerJob);
        return workflowTimerJob.getId();
    }

    @Override
    public void updateWorkflowTimerJob(UpdateWorkflowTimerJobDto updateWorkflowTimerJobDto) {
        WorkflowTimerJob workflowTimerJob = WorkflowTimerJob.builder()
                .build();
        LambdaUpdateChainWrapper<WorkflowTimerJob> updateChainWrapper;
        if (CollectionsUtil.isNotEmpty(updateWorkflowTimerJobDto.getWorkflowInstanceIds())){
            updateChainWrapper = workflowTimerJobDao.lambdaUpdate().in(WorkflowTimerJob::getWorkflowInstanceId, updateWorkflowTimerJobDto.getWorkflowInstanceIds());
        }else if (CollectionsUtil.isNotEmpty(updateWorkflowTimerJobDto.getWorkflowTaskIds())){
            updateChainWrapper =  workflowTimerJobDao.lambdaUpdate().in(WorkflowTimerJob::getWorkflowTaskInstanceId, updateWorkflowTimerJobDto.getWorkflowTaskIds());
        }else if (CollectionsUtil.isNotEmpty(updateWorkflowTimerJobDto.getIds())){
            updateChainWrapper =  workflowTimerJobDao.lambdaUpdate().in(WorkflowTimerJob::getId, updateWorkflowTimerJobDto.getIds());
        }else{
            throw new WorkflowRuntimeException("require instanceIds or ids or taskIds");
        }
        updateChainWrapper.set(WorkflowTimerJob::getStatus, updateWorkflowTimerJobDto.getStatus());
        updateChainWrapper.update();
    }

    @Override
    public void delete(String workflowInstanceId, String executorId) {
        WorkflowInstance workflowInstance = new WorkflowInstance();
        workflowInstance.setId(workflowInstanceId);
        workflowInstance.setDeleted(true);
        workflowInstance.setLastModifierId(executorId);
        workflowInstanceDao.updateById(workflowInstance);
    }

    @Override
    public void updateWorkflowInstance(UpdateWorkflowInstanceDto dto) {
        WorkflowInstance workflowInstance = new WorkflowInstance();
        workflowInstance.setId(dto.getId());
        workflowInstance.setFormData(dto.getFormData());
        workflowInstance.setStatus(dto.getStatus());
        workflowInstance.setLastModifierId(dto.getExecutorId());
        workflowInstance.setLastModifyTime(new Date());
        workflowInstanceDao.updateById(workflowInstance);
    }

    @Override
    public void updateWorkflowTaskInstance(UpdateWorkflowTaskInstanceDto dto) {
        WorkflowTaskInstance workflowTaskInstance = new WorkflowTaskInstance();
        workflowTaskInstance.setId(dto.getId());
        workflowTaskInstance.setStatus(dto.getStatus());
        if (workflowTaskInstance.getStatus() != null
                && WorkflowTaskInstance.WorkflowTaskInstanceStatus.COMPLETED.equals(workflowTaskInstance.getStatus())){
            workflowTaskInstance.setEndTime(new Date());
        }
        workflowTaskInstance.setExecutorId(dto.getExecutorId());
        workflowTaskInstanceDao.updateById(workflowTaskInstance);
    }

    @Override
    public String createWorkflowInstActivity(CreateWorkflowInstanceActivityDto createWorkflowTimerJobDto) {
        WorkflowInstanceActivity activity = new WorkflowInstanceActivity();
        activity.setContent(createWorkflowTimerJobDto.getContent());
        createWorkflowTimerJobDto.getContent().setDescription(createWorkflowTimerJobDto.getDescription());
        activity.setOperateName(createWorkflowTimerJobDto.getOperateName());
        activity.setOperatorId(createWorkflowTimerJobDto.getOperatorId());
        activity.setOperateTime(new Date());
        activity.setWorkflowInstanceId(createWorkflowTimerJobDto.getWorkflowInstanceId());
        activity.setWorkflowTaskId(createWorkflowTimerJobDto.getWorkflowTaskId());
        activity.setWorkflowTaskName(createWorkflowTimerJobDto.getWorkflowTaskName());
        workflowInstanceActivityDao.save(activity);
        return activity.getId();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        processService.setTimerTriggerHandler(taskTimer->{
            try{
                String processId = taskTimer.getTask().getProcessId();
                WorkflowTimerJob workflowTimerJob = workflowRuntimeQuery.getWorkflowTimerByJobId(taskTimer.getJobId());
                WorkflowInstance workflowInstance = workflowInstanceDao.lambdaQuery().eq(WorkflowInstance::getProcessId, processId).one();
                WorkflowTaskInstance workflowTaskInstance = workflowTaskInstanceDao.lambdaQuery().eq(WorkflowTaskInstance::getProcessTaskId, taskTimer.getTask().getId()).one();

                WorkflowTimerInstance workflowTimerInstance = new WorkflowTimerInstance();
                workflowTimerInstance.setTriggerTime(new Date());
                workflowTimerInstance.setWorkflowInstanceId(workflowInstance.getId());
                workflowTimerInstance.setWorkflowTaskId(workflowTaskInstance.getId());
                workflowTimerInstance.setWorkflowTimerJobId(workflowTimerJob.getId());
                workflowTimerInstanceDao.save(workflowTimerInstance);

                workflowRuntimeService.timerTriggered(workflowInstance, workflowTaskInstance, workflowTimerInstance, workflowTimerJob);
            }catch (Exception e){
                log.error("timer trigger error", e);
            }
        });
    }
}
